// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:build aix | | darwin | |蜻蜓| | freebsd | | linux | | netbsd | openbsd | solaris 
// /+build aix darwin drawnfly freebsd linux netbsd openbsd solaris 

// Fork、exec、wait等

package syscall

import (
	errorspkg "errors"
	"internal/bytealg"
	"runtime"
	"sync"
	"unsafe"
)

// 用Fork同步创建新文件描述符。
// 
// 我们希望fork/exec序列中的子级只继承我们想要的
// 文件描述符。为此，我们在exec上标记所有文件
// 描述符，然后在子文件中显式地
// 取消标记我们希望被执行程序保留的那些。
// Unix并没有让这变得容易：一般来说，没有办法在exec上分配一个新的文件描述符。相反，您必须分配描述符，然后在exec上将其标记为close。
// 如果在这两个事件之间发生分叉，则子级的exec 
// 将继承一个不需要的文件描述符。
// 
// 这个锁解决了这个争用：create new fd/mark close on exec 
// 操作是通过持有ForkLock进行读取完成的，而fork本身
// 是通过持有ForkLock进行写入完成的。至少，这是个想法。
// 有一些并发症。
// 
// 一些创建新文件描述符的系统调用可以阻止
// 任意长时间：在挂起的NFS服务器上打开或命名为
// 管道，在套接字上接受，等等。我们无法合理地抓住
// 这些操作的锁。
// 
// 继承某些文件描述符比继承其他描述符更糟糕。如果一个非恶意的孩子意外地继承了一个打开的普通文件，这没什么大不了的。另一方面，如果长寿命的子级意外地继承了管道的写入端，则该管道的读取器在该子级退出之前将看不到EOF，这可能导致父程序挂起。这是使用popen的线程化C程序中的一个常见问题。
// 
// 幸运的是，对于
// 继承来说，最重要的文件描述符不是那些需要花费任意长时间才能创建的描述符：管道立即返回，网络包使用
// 非阻塞I/O在侦听套接字上接受。
// 文件描述符创建操作使用叉锁的规则如下：
// 
// 1）管道。不阻塞。使用叉锁。
// 2）插座。不阻塞。使用叉锁。接受。如果使用非阻塞模式，则使用叉锁。
// 否则，与种族共存。打开。你可以阻止。如果可用，请使用O_CLOEXEC（Linux）。
// 否则，与种族共存。
// 5）Dup。不阻塞。使用叉锁。
// 在Linux上，可以使用fcntl F_DUPFD_CLOEXEC 
// 而不是ForkLock，但仅适用于dup（fd，-1）。

var ForkLock sync.RWMutex

// StringSlicePtr将字符串片段转换为指针片段
// 转换为以NUL结尾的字节数组。如果任何字符串包含NUL字节
// 此函数将暂停，而不是返回错误。
// 
// 已弃用：请改用SlicePtrFromString。
func StringSlicePtr(ss []string) []*byte {
	bb := make([]*byte, len(ss)+1)
	for i := 0; i < len(ss); i++ {
		bb[i] = StringBytePtr(ss[i])
	}
	bb[len(ss)] = nil
	return bb
}

// SlicePtrFromStrings将字符串片段转换为
// 指向以NUL结尾的字节数组的指针。如果任何字符串包含
// 一个NUL字节，则返回（nil，EINVAL）。
func SlicePtrFromStrings(ss []string) ([]*byte, error) {
	n := 0
	for _, s := range ss {
		if bytealg.IndexByteString(s, 0) != -1 {
			return nil, EINVAL
		}
		n += len(s) + 1 // /+1表示NUL 
	}
	bb := make([]*byte, len(ss)+1)
	b := make([]byte, n)
	n = 0
	for i, s := range ss {
		bb[i] = &b[n]
		copy(b[n:], s)
		n += len(s) + 1
	}
	return bb, nil
}

func CloseOnExec(fd int) { fcntl(fd, F_SETFD, FD_CLOEXEC) }

func SetNonblock(fd int, nonblocking bool) (err error) {
	flag, err := fcntl(fd, F_GETFL, 0)
	if err != nil {
		return err
	}
	if nonblocking {
		flag |= O_NONBLOCK
	} else {
		flag &^= O_NONBLOCK
	}
	_, err = fcntl(fd, F_SETFL, flag)
	return err
}

// 凭证保存由StartProcess启动的子进程假定的用户和组标识
// 。
type Credential struct {
	Uid         uint32   // 用户ID.
	Gid         uint32   // 组ID.
	Groups      []uint32 // 补充组ID。
	NoSetGroups bool     // 如果为true，则不设置补充组
}

// ProcAttr保留将应用于由StartProcess启动的新进程
// 的属性。
type ProcAttr struct {
	Dir   string    // 当前工作目录。
	Env   []string  // 环境。
	Files []uintptr // 文件描述符。
	Sys   *SysProcAttr
}

var zeroProcAttr ProcAttr
var zeroSysProcAttr SysProcAttr

func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
	var p [2]int
	var n int
	var err1 Errno
	var wstatus WaitStatus

	if attr == nil {
		attr = &zeroProcAttr
	}
	sys := attr.Sys
	if sys == nil {
		sys = &zeroSysProcAttr
	}

	p[0] = -1
	p[1] = -1

	// 将参数转换为C格式。
	argv0p, err := BytePtrFromString(argv0)
	if err != nil {
		return 0, err
	}
	argvp, err := SlicePtrFromStrings(argv)
	if err != nil {
		return 0, err
	}
	envvp, err := SlicePtrFromStrings(attr.Env)
	if err != nil {
		return 0, err
	}

	if (runtime.GOOS == "freebsd" || runtime.GOOS == "dragonfly") && len(argv[0]) > len(argv0) {
		argvp[0] = argv0p
	}

	var chroot *byte
	if sys.Chroot != "" {
		chroot, err = BytePtrFromString(sys.Chroot)
		if err != nil {
			return 0, err
		}
	}
	var dir *byte
	if attr.Dir != "" {
		dir, err = BytePtrFromString(attr.Dir)
		if err != nil {
			return 0, err
		}
	}

	// Setctty和Foreground都使用Ctty字段，但它们赋予它的含义略有不同。
	if sys.Setctty && sys.Foreground {
		return 0, errorspkg.New("both Setctty and Foreground set in SysProcAttr")
	}
	if sys.Setctty && sys.Ctty >= len(attr.Files) {
		return 0, errorspkg.New("Setctty set but Ctty not valid in child")
	}

	// 获取fork锁，这样就不会有其他线程
	// 在我们使用fork之前，在exec上创建尚未关闭的新FD。
	ForkLock.Lock()

	// 在exec上分配子状态管道关闭。
	if err = forkExecPipe(p[:]); err != nil {
		goto error
	}

	// 踢孩子。
	pid, err1 = forkAndExecInChild(argv0p, argvp, envvp, chroot, dir, attr, sys, p[1])
	if err1 != 0 {
		err = Errno(err1)
		goto error
	}
	ForkLock.Unlock()

	// 从管道读取子错误状态。
	Close(p[1])
	for {
		n, err = readlen(p[0], (*byte)(unsafe.Pointer(&err1)), int(unsafe.Sizeof(err1)))
		if err != EINTR {
			break
		}
	}
	Close(p[0])
	if err != nil || n != 0 {
		if n == int(unsafe.Sizeof(err1)) {
			err = Errno(err1)
		}
		if err == nil {
			err = EPIPE
		}

		// 子项失败；等待它退出，以确保
		// 僵尸不会累积。
		_, err1 := Wait4(pid, &wstatus, 0, nil)
		for err1 == EINTR {
			_, err1 = Wait4(pid, &wstatus, 0, nil)
		}
		return 0, err
	}

	// Read获得EOF，因此管道在exec上关闭，因此exec成功。
	return pid, nil

error:
	if p[0] >= 0 {
		Close(p[0])
		Close(p[1])
	}
	ForkLock.Unlock()
	return 0, err
}

// fork和exec的组合，注意线程安全。
func ForkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
	return forkExec(argv0, argv, attr)
}

// StartProcess为包操作系统包装ForkExec。
func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
	pid, err = forkExec(argv0, argv, attr)
	return pid, 0, err
}

// 在运行时包中实现。
func runtime_BeforeExec()
func runtime_AfterExec()

// execveLibc在使用libc syscall的操作系统上为非nil，在exec_libc.go中设置为execve；此
// 避免了对其他平台的构建依赖。
var execveLibc func(path uintptr, argv uintptr, envp uintptr) Errno
var execveDarwin func(path *byte, argv **byte, envp **byte) error
var execveOpenBSD func(path *byte, argv **byte, envp **byte) error

// Exec调用execve（2）系统调用。
func Exec(argv0 string, argv []string, envv []string) (err error) {
	argv0p, err := BytePtrFromString(argv0)
	if err != nil {
		return err
	}
	argvp, err := SlicePtrFromStrings(argv)
	if err != nil {
		return err
	}
	envvp, err := SlicePtrFromStrings(envv)
	if err != nil {
		return err
	}
	runtime_BeforeExec()

	var err1 error
	if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" || runtime.GOOS == "aix" {
		// RawSyscall不应在Solaris、illumos或AIX上使用。
		err1 = execveLibc(
			uintptr(unsafe.Pointer(argv0p)),
			uintptr(unsafe.Pointer(&argvp[0])),
			uintptr(unsafe.Pointer(&envvp[0])))
	} else if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
		// 达尔文也是如此。
		err1 = execveDarwin(argv0p, &argvp[0], &envvp[0])
	} else if runtime.GOOS == "openbsd" && (runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
		// 在OpenBSD上也是如此。
		err1 = execveOpenBSD(argv0p, &argvp[0], &envvp[0])
	} else {
		_, _, err1 = RawSyscall(SYS_EXECVE,
			uintptr(unsafe.Pointer(argv0p)),
			uintptr(unsafe.Pointer(&argvp[0])),
			uintptr(unsafe.Pointer(&envvp[0])))
	}
	runtime_AfterExec()
	return err1
}
